/*
* runimg c/c++ sdk
* Copyright 2015 guangleihe@gmail.com.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http ://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

#include "runimg/runimgservice.h"
#include <string>
#include <sstream>
#include <stdio.h>
#include "base/logging.h"
#include "base/basedefines.h"
#include "json/json.h"
#include "base/base64.h"


namespace runimg {

#ifdef WIN32
#define runimg_sleep(x) Sleep((x) * 1000)
#else
#define runimg_sleep(x) sleep(x)
#endif

static const int MAX_URL_SIZE = 1024;

const char INFOR_DATA[] = "date";
const char DEFAULT_VERSION[] = "1.0";

const char TOKEN_ID[] = "token_id";

const char EXPIRED[] = "expired";

const char RECORD_INTERVAL[] = "rec_inv";
const char JSON_RECORD_START_INTERVAL[] = "st";
const char JSON_RECORD_END_INTERVAL[]   = "et";


const char IMG_TYPE[] = "img_type";
const char IMG_OPT[] = "img_opt";
const char TIMESTAMP[] = "timestamp";
const char VERSION[] = "version";

const char SIGNATURE[] = "signature";


const char JSON_STATUS[] = "status";
const char JSON_RECORD[] = "records";
const char JSON_DATE[] = "date";
const char JSON_TIMESTAMP[] = "timestamp";
const char JSON_URL[] = "url";

const uint32 MAX_IMAGE_PROCESS_SIZE = 4096;
const uint32 MIN_IMAGE_PROCESS_SIZE = 1;
const uint32 MIN_PROPORTION_VALUE   = 1;
const uint32 MAX_PROPORTION_VALUE   = 1000;
const time_t NEWWEST_TIME           = 0;

////////////////////////////////////////////////////////////////////////////////
ImageOperator::ImageOperator() {
  Reset();
}

void ImageOperator::Reset() {
  zoom_height_            = DEFAULT_VALUE;
  zoom_width_             = DEFAULT_VALUE;
  is_process_             = DEFAULT_VALUE;
  proportion_             = DEFAULT_VALUE;
  clipper_height_         = DEFAULT_VALUE;
  clipper_width_          = DEFAULT_VALUE;
  clipper_rect_           = DEFAULT_VALUE;
  rotation_               = DEFAULT_VALUE;
  image_sharpen_          = DEFAULT_VALUE;
  blurry_radius_          = DEFAULT_VALUE;
  blurry_sigma_           = DEFAULT_VALUE;
  brightness_             = DEFAULT_VALUE;
  contrast_               = DEFAULT_VALUE;
  jpg_relative_qulity_    = DEFAULT_VALUE;
  jpg_absolutely_qulity_  = DEFAULT_VALUE;
  image_format_           = FORMAT_SRC;
  clipper_radius_         = DEFAULT_VALUE;
  clipper_type_           = SOURCE;
  clipper_chunk_          = DEFAULT_VALUE;
  clipper_direct_         = CLIPPER_X;
  clipper_index_          = DEFAULT_VALUE;
  clipper_x_              = DEFAULT_VALUE;
  clipper_y_              = DEFAULT_VALUE;
  clipper_w_              = DEFAULT_VALUE;
  clipper_h_              = DEFAULT_VALUE;
}

const std::string ImageOperator::ToString() {
  Json::Value value;
  // 1. Zoom Settings
  if(zoom_height_ != DEFAULT_VALUE) {
    value[JSON_IMAGE_ZOOM_HEIGHT] = zoom_height_;
  }
  if(zoom_width_ != DEFAULT_VALUE) {
    value[JSON_IMAGE_ZOOM_WIDTH] = zoom_width_;
  }
  if(is_process_ != DEFAULT_VALUE) {
    value[JSON_IMAGE_ZOOM_IS_PROCESS] = is_process_;
  }
  if(proportion_ != DEFAULT_VALUE) {
    value[JSON_IMAGE_ZOOM_PROPORTION] = proportion_;
  }
  // 2. Clipper Settings
  if(clipper_height_ != DEFAULT_VALUE
      && clipper_width_ != DEFAULT_VALUE
      && clipper_rect_ > 0 && clipper_rect_ < 10) {
    std::stringstream ss;
    ss << clipper_height_ << "x" << clipper_width_ << "-" << clipper_rect_;
    value[JSON_CLIPPER_RECT] = ss.str();
  }
  if(clipper_radius_ != DEFAULT_VALUE
      && ClipperTypeToUint32(clipper_type_) != DEFAULT_VALUE) {
    std::stringstream ss;
    ss << clipper_radius_ << "-" << ClipperTypeToUint32(clipper_type_);
    value[JSON_CLIPPER_RADIOUS] = ss.str();
  }
  if(clipper_chunk_ != DEFAULT_VALUE
      && clipper_index_ != DEFAULT_VALUE
      && ClipperDirectToString(clipper_direct_) != NULL) {
    std::stringstream ss;
    ss << clipper_chunk_ << ClipperDirectToString(clipper_direct_)
       << "-" << clipper_index_;
    value[JSON_CLIPPER_INDEX] = ss.str();
  }
  if(clipper_x_ != DEFAULT_VALUE
      && clipper_y_ != DEFAULT_VALUE
      && clipper_w_ != DEFAULT_VALUE
      && clipper_h_ != DEFAULT_VALUE) {
    std::stringstream ss;
    ss << clipper_x_ << "-" << clipper_y_ << "-"
       << clipper_w_ << "-" << clipper_h_;
    value[JSON_CLIPPER_POINT] = ss.str();
  }
  // Rotator
  if(rotation_ != DEFAULT_VALUE) {
    value[JSON_ROTATION] = rotation_;
  }
  // Sharpen
  if(image_sharpen_ != DEFAULT_VALUE) {
    value[JSON_SHARPEN] = image_sharpen_;
  }
  // Blurry
  if(blurry_radius_ != DEFAULT_VALUE && blurry_sigma_ != DEFAULT_VALUE) {
    std::stringstream ss;
    ss << blurry_radius_ << "-" << blurry_sigma_;
    value[JSON_BLURRY] = ss.str();
  }
  // Brightness
  if(brightness_ != DEFAULT_VALUE) {
    value[JSON_BRIGHTNESS] = brightness_;
  }
  // Contrast
  if(contrast_ != DEFAULT_VALUE) {
    value[JSON_CONTRAST] = contrast_;
  }
  // Format
  if(ImageFormatToString(image_format_) != NULL
      && image_format_ != FORMAT_SRC) {
    value[JSON_IMAGE_FORMAT] = ImageFormatToString(image_format_);
  }
  if(image_format_ == FORMAT_JPG && jpg_relative_qulity_ != DEFAULT_VALUE) {
    value[JSON_JPG_RELATIVE_QULITY] = jpg_relative_qulity_;
  }
  if(image_format_ == FORMAT_JPG && jpg_absolutely_qulity_ != DEFAULT_VALUE) {
    value[JSON_JPG_ABSOLUTELY_QULITY] = jpg_absolutely_qulity_;
  }
  if(image_format_ == FORMAT_JPG
      && ProgressiveToUint32(progressive_) != DEFAULT_VALUE) {
    value[JSON_JPG_PROGRESSIVE] = ProgressiveToUint32(progressive_);
  }
  Json::FastWriter fw;
  return runimg::Base64::Encode(fw.write(value));
}

bool ImageOperator::SetImageZoom(uint32 height, uint32 width, bool is_process) {
  if(height < MIN_IMAGE_PROCESS_SIZE
      || height > MAX_IMAGE_PROCESS_SIZE
      || width < MIN_IMAGE_PROCESS_SIZE
      || width > MAX_IMAGE_PROCESS_SIZE) {
    LOG(ERROR) << "The size of the zoom out of the range [1, 4096]";
    return false;
  }
  zoom_height_ = height;
  zoom_width_  = width;
  if(is_process) {
    is_process_ = 0;
  } else {
    is_process_ = 1;
  }
  return true;
}

bool ImageOperator::SetImageZoom(uint32 proportion) {
  if(proportion < MIN_PROPORTION_VALUE || proportion > MAX_PROPORTION_VALUE) {
    LOG(INFO) << "The proportion value out of the range [1, 1000]";
    return false;
  }
  proportion_ = proportion;
  return true;
}

//
bool ImageOperator::SetImageClipper(uint32 height, uint32 width, uint32 rect) {
  if(height < MIN_IMAGE_PROCESS_SIZE
      || height > MAX_IMAGE_PROCESS_SIZE
      || width < MIN_IMAGE_PROCESS_SIZE
      || width > MAX_IMAGE_PROCESS_SIZE) {
    LOG(ERROR) << "The size of the clipper out of the range [1, 4096]";
    return false;
  }
  if(rect < 1 || rect > 9) {
    LOG(ERROR) << "The clipper rect of the image out of range [1, 9]";
    return false;
  }
  clipper_height_ = height;
  clipper_width_  = width;
  clipper_rect_   = rect;
  return true;
}

bool ImageOperator::SetImageClipper(uint32 radius, ClipperType clipper_type) {
  if(radius < 1 || radius > 4096) {
    LOG(ERROR) << "The clipper radius out of the range [1, 4096]";
    return false;
  }
  if(ClipperTypeToUint32(clipper_type) == DEFAULT_VALUE) {
    LOG(ERROR) << "The clipper type setting error";
    return false;
  }
  clipper_radius_ = radius;
  clipper_type_   = clipper_type;
  return true;
}

bool ImageOperator::SetImageClipper(uint32 chunk,
                                    ClipperDirect clipper_direct,
                                    uint32 index) {
  if(index < 1) {
    LOG(ERROR) << "The index small than 1";
    return false;
  }
  clipper_chunk_  = chunk;
  clipper_direct_ = clipper_direct;
  clipper_index_  = index;
  return true;
}

bool ImageOperator::SetImageClipper(uint32 x, uint32 y,
                                    uint32 width, uint32 height) {
  if(width < 1 || width > 4096 || height < 1 || height > 4096) {
    LOG(INFO) << "The Clipper width or height out of the range [1, 4096]";
    return false;
  }
  clipper_x_ = x;
  clipper_y_ = y;
  clipper_w_ = width;
  clipper_h_ = height;
  return true;
}

//
bool ImageOperator::SetImageRotator(uint32 rotation) {
  if(rotation > 360) {
    LOG(ERROR) << "The rotation out of the range [0, 360]";
    return false;
  }
  rotation_ = rotation;
  return true;

}
//
bool ImageOperator::SetImageSharpen(uint32 sharpening) {
  if(sharpening < 50 || sharpening > 399) {
    LOG(ERROR) << "The image sharpen out of the range [50, 399]";
  }
  image_sharpen_ = sharpening;
  return true;

}
//
bool ImageOperator::SetImageBlurry(uint32 radius, uint32 sigma) {
  if(radius < 1 || radius > 50) {
    LOG(ERROR) << "The Blurry radius out of the range [1, 50]";
    return false;
  }
  if(sigma < 1 || sigma > 50) {
    LOG(ERROR) << "The Blurry sigma out of the range [1, 50]";
    return false;
  }
  blurry_radius_ = radius;
  blurry_sigma_ = sigma;
  return true;
}
bool ImageOperator::SetImageBrightness(int brightness) {
  if(brightness < -100 || brightness > 100) {
    LOG(ERROR) << "The image brightness out of range [-100, 100]";
    return false;
  }
  brightness_ = brightness;
  return true;
}
bool ImageOperator::SetImageContrast(int contrast) {
  if(contrast < -100 || contrast > 100) {
    LOG(ERROR) << "The image contrast out of range [-100, 100]";
    return false;
  }
  contrast_ = contrast;
  return true;
}
//
bool ImageOperator::SetJpgImageRelativeQuality(uint32 quality) {
  if(quality < 1 || quality > 100) {
    LOG(ERROR) << "The Jpg image relative quality out of the range [1, 1000]";
    return false;
  }
  jpg_relative_qulity_ = quality;
  return true;
}
bool ImageOperator::SetJpgImageAbsolutelyQuality(uint32 quality) {
  if(quality < 1 || quality > 100) {
    LOG(ERROR) << "The Jpg image absolute quality out of the range [1, 1000]";
    return false;
  }
  jpg_absolutely_qulity_ = quality;
  return true;
}
bool ImageOperator::SetJpgProgressive(ProgressiveType progressive) {
  if(ProgressiveToUint32(progressive) == DEFAULT_VALUE) {
    LOG(ERROR) << "The Jpg image progressive type error";
    return false;
  }
  progressive_ = progressive;
  return true;
}
//
bool ImageOperator::SetImageFormat(ImageFormat image_format) {
  if(ImageFormatToString(image_format) == NULL) {
    LOG(ERROR) << "The image format settings failure";
    return false;
  }
  image_format_ = image_format;
  return true;
}

// -----------------------------------------------------------------------------
uint32 ImageOperator::ClipperTypeToUint32(ClipperType clipper_type) {
  if(clipper_type == SOURCE) {
    return 0;
  } else if (clipper_type == MIN_SQUARE) {
    return 1;
  } else if (clipper_type == ROUND_RECTANGLE) {
    return 2;
  }
  return DEFAULT_VALUE;
}

const char *ImageOperator::ClipperDirectToString(ClipperDirect clipper_direct) {
  if(clipper_direct == CLIPPER_X) {
    return "x";
  } else if (clipper_direct == CLIPPER_Y) {
    return "y";
  }
  return NULL;
}

const char *ImageOperator::ImageFormatToString(ImageFormat image_format) {
  switch (image_format) {
  case runimg::FORMAT_SRC:
    return "src";
  case runimg::FORMAT_JPG:
    return "jpg";
  case runimg::FORMAT_PNG:
    return "png";
  case runimg::FORMAT_WEBP:
    return "webp";
  case runimg::FORMAT_BMP:
    return "bmp";
  case runimg::FORMAT_GIT:
    return "gif";
  }
  return NULL;
}

uint32 ImageOperator::ProgressiveToUint32(ProgressiveType progressive) {
  switch (progressive) {
  case runimg::ENABLE_PROGRESSIVE:
    return 1;
  case runimg::DISABLE_PROGRESSIVE:
    return 0;
  }
  return DEFAULT_VALUE;
}

////////////////////////////////////////////////////////////////////////////////
UrlCreator::UrlCreator(const std::string token_id,
                       const std::string secret_key,
                       ImageType image_type,
                       uint32 expired,
                       ImageOperator::Ptr image_operator)
  : token_id_(token_id),
    secret_key_(secret_key),
    image_type_(image_type),
    expired_(expired),
    version_(DEFAULT_VERSION),
    image_operator_(image_operator),
    timestamp_(0),
    start_time_(NEWWEST_TIME),
    end_time_(NEWWEST_TIME) {
}

const std::string UrlCreator::ToUrlString() {
  timestamp_ = time(NULL);
  std::map<std::string, std::string> key_values;

  key_values[TOKEN_ID]        = token_id_;
  key_values[EXPIRED]         = std::to_string(expired_);
  key_values[IMG_TYPE]        = ImageTypeToString(image_type_);
  if(image_operator_) {
    key_values[IMG_OPT]       = image_operator_->ToString();
  }
  key_values[TIMESTAMP]       = std::to_string((uint32)timestamp_);
  key_values[VERSION]         = version_;
  key_values[RECORD_INTERVAL] = RecordIntervalTime();
  key_values[SIGNATURE]       = CalculateSignature(key_values, secret_key_);
  return CalculateParameters(key_values);
}

bool UrlCreator::SetRecordInterval(time_t start_time, time_t end_time) {
  start_time_ = start_time;
  end_time_   = end_time;
  return true;
}

const std::string UrlCreator::RecordIntervalTime() {
  Json::Value value;
  Json::FastWriter fw;
  value[JSON_RECORD_START_INTERVAL]   = start_time_;
  value[JSON_RECORD_END_INTERVAL]     = end_time_;
  return runimg::Base64::Encode(fw.write(value));
}

const std::string UrlCreator::CalculateSignature(
  std::map<std::string, std::string> &key_values,
  const std::string token_key) {
  std::string key_string;
  std::map<std::string, std::string>::iterator iter = key_values.begin();
  while(iter != key_values.end()) {
    // std::cout << iter->first << "\t\t\t" << iter->second << std::endl;
    key_string += iter->first;
    key_string += "=";
    key_string += iter->second;
    iter++;
    if(iter != key_values.end()) {
      key_string += "&";
    } else {
      break;
    }
  }
  std::cout << key_string << std::endl;
  std::string signature;
  runimg::HelpMethos::HmacSha1ToBase64(token_key, key_string, signature);
  std::cout << signature << std::endl;
  return signature;
}

const std::string UrlCreator::CalculateParameters(
  std::map<std::string, std::string> &key_values) {
  std::string key_string;
  std::map<std::string, std::string>::iterator iter = key_values.begin();
  while(iter != key_values.end()) {
    // std::cout << iter->first << "\t\t\t" << iter->second << std::endl;
    key_string += iter->first;
    key_string += "=";
    key_string += runimg::HelpMethos::URLEncode(iter->second);
    iter++;
    if(iter != key_values.end()) {
      key_string += "&";
    } else {
      break;
    }
  }
  return key_string;
}

const char *UrlCreator::ImageTypeToString(ImageType image_type) {
  switch (image_type) {
  case runimg::TYPE_1D:
    return "1d";
  case runimg::TYPE_2D:
    return "2d";
  case runimg::TYPE_2D_0_0:
    return "2d_0_0";
  case runimg::TYPE_2D_0_1:
    return "2d_0_1";
  case runimg::TYPE_2D_1_0:
    return "2d_1_0";
  case runimg::TYPE_2D_1_1:
    return "2d_1_1";
  case runimg::TYPE_4D:
    return "4d";
  case runimg::TYPE_4D_0_0:
    return "4d_0_0";
  case runimg::TYPE_4D_0_1:
    return "4d_0_1";
  case runimg::TYPE_4D_0_2:
    return "4d_0_2";
  case runimg::TYPE_4D_0_3:
    return "4d_0_3";
  case runimg::TYPE_4D_1_0:
    return "4d_1_0";
  case runimg::TYPE_4D_1_1:
    return "4d_1_1";
  case runimg::TYPE_4D_1_2:
    return "4d_1_2";
  case runimg::TYPE_4D_1_3:
    return "4d_1_3";
  case runimg::TYPE_4D_2_0:
    return "4d_2_0";
  case runimg::TYPE_4D_2_1:
    return "4d_2_1";
  case runimg::TYPE_4D_2_2:
    return "4d_2_2";
  case runimg::TYPE_4D_2_3:
    return "4d_2_3";
  case runimg::TYPE_4D_3_0:
    return "4d_3_0";
  case runimg::TYPE_4D_3_1:
    return "4d_3_1";
  case runimg::TYPE_4D_3_2:
    return "4d_3_2";
  case runimg::TYPE_4D_3_3:
    return "4d_3_3";
  case runimg::TYPE_HI_RES_800:
    return "hi_res_800";
  case runimg::TYPE_HI_RES_5000:
    return "hi_res_5000";
  }
  return NULL;
}

////////////////////////////////////////////////////////////////////////////////
//------------------------------------------------------------------------------
RunimgService::RunimgService()
  : curl_service_(runimg::CurlService::InitCurlService()) {
}

RunimgService::~RunimgService() {
}


UpdateStanza RunimgService::GetImageUrl(const std::string url_base,
                                        UrlCreator::Ptr url_creator) {
  std::string server_url = url_base + url_creator->ToUrlString();
  LOG(INFO) << server_url;
  std::string rep;
  UpdateStanza stanza;
  bool res = curl_service_->SyncProcessGetRequest(server_url, rep);
  if(res) {
    ParseStanza(rep, stanza);
  }
  LOG(INFO) << rep;
  return stanza;
}

bool RunimgService::GetImageByUrl(const std::string image_url,
                                  std::string *result_image) {
  LOG(INFO) << image_url;
  if(!curl_service_->SyncProcessGetRequest(image_url, *result_image)) {
    return false;
  }
  return true;
}

bool RunimgService::ParseStanza(const std::string rep, UpdateStanza &stanza) {
  Json::Value value;
  Json::Reader reader;
  if(!reader.parse(rep, value)) {
    LOG_INFO << reader.getFormattedErrorMessages();
    return false;
  }
  stanza.status = value[JSON_STATUS].asUInt();
  if(stanza.status != 200) {
    return false;
  }
  Json::Value json_records = value[JSON_RECORD];
  std::vector<Record> &records = stanza.records;
  for(std::size_t i = 0; i < json_records.size(); i++) {
    Record rec;
    rec.date = json_records[i][JSON_DATE].asString();
    rec.timestamp = json_records[i][JSON_TIMESTAMP].asUInt();
    rec.url = json_records[i][JSON_URL].asString();
    records.push_back(rec);
  }
  return true;
}

}  // namespace runimg
